// ======================================================================
// \title  FileDownlink.hpp
// \author bocchino, mstarch
// \brief  hpp file for FileDownlink component implementation class
//
// \copyright
// Copyright 2009-2015, by the California Institute of Technology.
// ALL RIGHTS RESERVED.  United States Government Sponsorship
// acknowledged.
// ======================================================================

#ifndef Svc_FileDownlink_HPP
#define Svc_FileDownlink_HPP

#include <Fw/FilePacket/FilePacket.hpp>
#include <Os/File.hpp>
#include <Os/Mutex.hpp>
#include <Os/Queue.hpp>
#include <Svc/FileDownlink/FileDownlinkComponentAc.hpp>
#include <config/FileDownlinkCfg.hpp>

namespace Svc {

class FileDownlink final : public FileDownlinkComponentBase {
    friend class FileDownlinkTester;

  private:
    // ----------------------------------------------------------------------
    // Types
    // ----------------------------------------------------------------------

    //! The Mode class
    class Mode {
        friend class FileDownlinkTester;

      public:
        //! The Mode type
        typedef enum { IDLE, DOWNLINK, CANCEL, WAIT, COOLDOWN } Type;

      public:
        //! Constructor
        Mode() : m_value(IDLE) {}

      public:
        //! Set the Mode value
        void set(const Type value) {
            this->m_mutex.lock();
            this->m_value = value;
            this->m_mutex.unLock();
        }

        //! Get the Mode value
        Type get() {
            this->m_mutex.lock();
            const Type value = this->m_value;
            this->m_mutex.unLock();
            return value;
        }

      private:
        //! The Mode value
        Type m_value;

        //! The Mode mutex
        Os::Mutex m_mutex;
    };

    //! Class representing an outgoing file
    class File {
        friend class FileDownlinkTester;

      public:
        //! Constructor
        File() : m_size(0) {}

      private:
        //! The source file name
        Fw::LogStringArg m_sourceName;

        //! The destination file name
        Fw::LogStringArg m_destName;

        //! The underlying OS file
        Os::File m_osFile;

        //! The file size
        U32 m_size;

        //! The checksum for the file
        CFDP::Checksum m_checksum;

      public:
        //! Open the OS file for reading and initialize the checksum
        Os::File::Status open(const char* const sourceFileName,  //!< The source file name
                              const char* const destFileName     //!< The destination file name
        );

        //! Read bytes from the OS file and update the checksum
        Os::File::Status read(U8* const data, const U32 byteOffset, const U32 size);

        //! Get the checksum
        void getChecksum(CFDP::Checksum& checksum) { checksum = this->m_checksum; }

        //! Get the source file name
        Fw::LogStringArg& getSourceName(void) { return this->m_sourceName; }

        //! Get the destination file name
        Fw::LogStringArg& getDestName(void) { return this->m_destName; }

        //! Get the underlying OS file
        Os::File& getOsFile(void) { return this->m_osFile; }

        //! Get the file size
        U32 getSize(void) { return this->m_size; }
    };

    //! Class to record files sent
    class FilesSent {
        friend class FileDownlinkTester;

      public:
        //! Construct a FilesSent object
        FilesSent(FileDownlink* const fileDownlink) : m_sent_file_count(0), m_fileDownlink(fileDownlink) {}

      public:
        //! Record a file sent
        void fileSent() {
            ++this->m_sent_file_count;
            this->m_fileDownlink->tlmWrite_FilesSent(m_sent_file_count);
        }

      private:
        //! The total number of file sent
        U32 m_sent_file_count;

        //! The enclosing FileDownlink object
        FileDownlink* const m_fileDownlink;
    };

    //! Class to record packets sent
    class PacketsSent {
        friend class FileDownlinkTester;

      public:
        //! Construct a PacketsSent object
        PacketsSent(FileDownlink* const fileDownlink) : m_sent_packet_count(0), m_fileDownlink(fileDownlink) {}

      public:
        //! Record a packet sent
        void packetSent() {
            ++this->m_sent_packet_count;
            this->m_fileDownlink->tlmWrite_PacketsSent(m_sent_packet_count);
        }

      private:
        //! The total number of downlinked packets
        U32 m_sent_packet_count;

        //! The enclosing FileDownlink object
        FileDownlink* const m_fileDownlink;
    };

    //! Class to record warnings
    class Warnings {
        friend class FileDownlinkTester;

      public:
        //! Construct a Warnings object
        Warnings(FileDownlink* const fileDownlink) : m_warning_count(0), m_fileDownlink(fileDownlink) {}

      public:
        //! Issue a File Open Error warning
        void fileOpenError();

        //! Issue a File Read Error warning
        void fileRead(const Os::File::Status status);

      private:
        //! Record a warning
        void warning() {
            ++this->m_warning_count;
            this->m_fileDownlink->tlmWrite_Warnings(m_warning_count);
        }

      private:
        //! The total number of warnings
        U32 m_warning_count;

        //! The enclosing FileDownlink object
        FileDownlink* const m_fileDownlink;
    };

    //! Sources of send file requests
    enum CallerSource { COMMAND, PORT };

#define FILE_ENTRY_FILENAME_LEN 101

    //! Used to track a single file downlink request
    struct FileEntry {
        char srcFilename[FILE_ENTRY_FILENAME_LEN];   // Name of requested file
        char destFilename[FILE_ENTRY_FILENAME_LEN];  // Name of requested file
        U32 offset;
        U32 length;
        CallerSource source;  // Source of the downlink request
        FwOpcodeType opCode;  // Op code of command, only set for CMD sources.
        U32 cmdSeq;           // CmdSeq number, only set for CMD sources.
        U32 context;          // Context id of request, only set for PORT sources.
    };

    //! Enumeration for packet types
    //! Each type has a buffer to store it.
    enum PacketType { FILE_PACKET, CANCEL_PACKET, COUNT_PACKET_TYPE };

  public:
    // ----------------------------------------------------------------------
    // Construction, initialization, and destruction
    // ----------------------------------------------------------------------

    //! Construct object FileDownlink
    //!
    FileDownlink(const char* const compName  //!< The component name
    );

    //! Configure FileDownlink component
    //!
    void configure(U32 timeout,        //!< Timeout threshold (milliseconds) while in WAIT state
                   U32 cooldown,       //!< Cooldown (in ms) between finishing a downlink and starting the next file.
                   U32 cycleTime,      //!< Rate at which we are running
                   U32 fileQueueDepth  //!< Max number of items in file downlink queue
    );

    //! Cleans up file queue before dispatching to underlying component
    void deinit();

    //! Start FileDownlink component
    //! The component must be configured with configure() before starting.
    //!
    void preamble();

    //! Destroy object FileDownlink
    //!
    ~FileDownlink();

  private:
    // ----------------------------------------------------------------------
    // Handler implementations for user-defined typed input ports
    // ----------------------------------------------------------------------

    //! Handler implementation for Run
    //!
    void Run_handler(const FwIndexType portNum,  //!< The port number
                     U32 context                 //!< The call order
    );

    //! Handler implementation for SendFile
    //!
    Svc::SendFileResponse SendFile_handler(
        const FwIndexType portNum,            /*!< The port number*/
        const Fw::StringBase& sourceFilename, /*!< Path of file to downlink*/
        const Fw::StringBase& destFilename,   /*!< Path to store downlinked file at*/
        U32 offset, /*!< Amount of data in bytes to downlink from file. 0 to read until end of file*/
        U32 length  /*!< Amount of data in bytes to downlink from file. 0 to read until end of file*/
    );

    //! Handler implementation for bufferReturn
    //!
    void bufferReturn_handler(const FwIndexType portNum,  //!< The port number
                              Fw::Buffer& fwBuffer);

    //! Handler implementation for pingIn
    //!
    void pingIn_handler(const FwIndexType portNum, /*!< The port number*/
                        U32 key                    /*!< Value to return to pinger*/
    );

  private:
    // ----------------------------------------------------------------------
    // Command handler implementations
    // ----------------------------------------------------------------------

    //! Implementation for FileDownlink_SendFile command handler
    //!
    void SendFile_cmdHandler(const FwOpcodeType opCode,               //!< The opcode
                             const U32 cmdSeq,                        //!< The command sequence number
                             const Fw::CmdStringArg& sourceFilename,  //!< The name of the on-board file to send
                             const Fw::CmdStringArg& destFilename  //!< The name of the destination file on the ground
    );

    //! Implementation for FileDownlink_Cancel command handler
    //!
    void Cancel_cmdHandler(const FwOpcodeType opCode,  //!< The opcode
                           const U32 cmdSeq            //!< The command sequence number
    );

    //! Implementation for FILE_DWN_SEND_PARTIAL command handler
    //!
    void SendPartial_cmdHandler(
        FwOpcodeType opCode,                     //!< The opcode
        U32 cmdSeq,                              //!< The command sequence number
        const Fw::CmdStringArg& sourceFilename,  //!< The name of the on-board file to send
        const Fw::CmdStringArg& destFilename,    //!< The name of the destination file on the ground
        U32 startOffset,                         //!< Starting offset of the source file
        U32 length  //!< Number of bytes to send from starting offset. Length of 0 implies until the end of the file
    );

  private:
    // ----------------------------------------------------------------------
    // Private helper methods
    // ----------------------------------------------------------------------

    void sendFile(
        const char* sourceFilename,  //!< The name of the on-board file to send
        const char* destFilename,    //!< The name of the destination file on the ground
        U32 startOffset,             //!< Starting offset of the source file
        U32 length  //!< Number of bytes to send from starting offset. Length of 0 implies until the end of the file
    );

    // Individual packet transfer functions
    Os::File::Status sendDataPacket(U32& byteOffset);
    void sendCancelPacket();
    void sendEndPacket();
    void sendStartPacket();
    void sendFilePacket(const Fw::FilePacket& filePacket);

    // State-helper functions
    void exitFileTransfer();
    void enterCooldown();

    // Function to acquire a buffer internally
    void getBuffer(Fw::Buffer& buffer, PacketType type);
    // Downlink the "next" packet
    void downlinkPacket();
    // Finish the file transfer
    void finishHelper(bool is_cancel);
    // Convert internal status enum to a command response;
    Fw::CmdResponse statusToCmdResp(SendFileStatus status);
    // Send response after completing file downlink
    void sendResponse(SendFileStatus resp);

  private:
    // ----------------------------------------------------------------------
    // Member variables
    // ----------------------------------------------------------------------

    //! Whether the configuration function has been called.
    bool m_configured;

    //! File downlink queue
    Os::Queue m_fileQueue;

    //! Buffer's memory backing
    U8 m_memoryStore[COUNT_PACKET_TYPE][FILEDOWNLINK_INTERNAL_BUFFER_SIZE];

    //! The mode
    Mode m_mode;

    //! The file
    File m_file;

    //! Files sent
    FilesSent m_filesSent;

    //! Packets sent
    PacketsSent m_packetsSent;

    //! Warnings
    Warnings m_warnings;

    //! The current sequence index
    U32 m_sequenceIndex;

    //! Timeout threshold (milliseconds) while in WAIT state
    U32 m_timeout;

    //! Cooldown (in ms) between finishing a downlink and starting the next file.
    U32 m_cooldown;

    //! current time residing in WAIT state
    U32 m_curTimer;

    //! rate (milliseconds) at which we are running
    U32 m_cycleTime;

    ////! Buffer for sending file data
    Fw::Buffer m_buffer;

    //! Buffer size for file data
    U32 m_bufferSize;

    //! Current byte offset in file
    U32 m_byteOffset;

    //! Amount of bytes left to read
    U32 m_endOffset;

    //! Set to true when all data packets have been sent
    Fw::FilePacket::Type m_lastCompletedType;

    //! Last buffer used
    U32 m_lastBufferId;

    //! Current in progress file entry from queue
    struct FileEntry m_curEntry;

    //! Incrementing context id used to unique identify a specific downlink request
    U32 m_cntxId;
};

}  // end namespace Svc

#endif
